classdef WirelessAdapter < ClockHandler
    %WirelessAdapter Summary of this class goes here
    %   Detailed explanation goes here
    
    properties(SetAccess = 'public', GetAccess = 'public')
        %the adapter has a single queueu for all links
        packet_transmission_queue = [];
        packet_transmission_queue_size_in_bytes = 0;
        link_rate_generator = [];
        owning_node = [];
        number_of_bytes_transferred_per_channel = [];
    end
    
    methods
        function obj=WirelessAdapter(link_rate_generator, owning_node)
            if nargin > 0
                obj.link_rate_generator = link_rate_generator;
                obj.owning_node = owning_node;
                if obj.link_rate_generator.number_of_channels>0
                    obj.number_of_bytes_transferred_per_channel(obj.link_rate_generator.number_of_channels) = 0;
                end
            end
            %register on clock tick
            ck = ClockSingleton.instance();
            ck.register_for_clock_ticks(obj);
        end
        
        function transmit_packets(obj, packets_to_transmit)
            packets_size_in_bytes = 0;
            number_of_packet_to_deposit = 0;
            queue_max_size_in_bytes = ParamsSingleton.instance().adapter_max_queue_size_in_bytes;
            
            new_multi_packet_to_deposit = [];
            
            for i=1:length(packets_to_transmit)
                curr_multi_packet = packets_to_transmit(i);
                curr_packet = curr_multi_packet.packet;
                new_packets_size_in_bytes = packets_size_in_bytes+curr_packet.size_in_bytes*curr_multi_packet.num_of_packets;
                if obj.packet_transmission_queue_size_in_bytes+new_packets_size_in_bytes <= queue_max_size_in_bytes
                    number_of_packet_to_deposit=number_of_packet_to_deposit+1;
                    packets_size_in_bytes = new_packets_size_in_bytes;
                else
                    %break current multi packet in two
                    num_of_packets_that_can_fit = floor((queue_max_size_in_bytes-obj.packet_transmission_queue_size_in_bytes) / curr_packet.size_in_bytes);
                    if num_of_packets_that_can_fit > 0
                        new_multi_packet_to_deposit = curr_multi_packet.split_multi_packet(curr_multi_packet.packet_id_range_min+num_of_packets_that_can_fit);
                        packets_size_in_bytes = packets_size_in_bytes+curr_packet.size_in_bytes*new_multi_packet_to_deposit.num_of_packets;
                    end
                    break;
                end
            end
            
            if number_of_packet_to_deposit>0
                obj.packet_transmission_queue_size_in_bytes = obj.packet_transmission_queue_size_in_bytes+packets_size_in_bytes;
                % PR1 - use loop to insert instead of concat
                obj.packet_transmission_queue = [obj.packet_transmission_queue packets_to_transmit(1:number_of_packet_to_deposit) new_multi_packet_to_deposit];
            end
        end
        
        function clock_tick_handler(obj)
            if length(obj.owning_node.neighbours)==0
                return;
            end
            
            %transmit data
            [fully_transmitted_packet_indixes new_multi_packet_vec num_of_new_multi_packets] = obj.transmit_data(ParamsSingleton.instance().clock_tick_delta_in_msec);
            if (length(fully_transmitted_packet_indixes)==0)&&(num_of_new_multi_packets==0)
                return;
            end
            
            %get fully transmitted packets and use the router to deposit
            %them
            
            if (length(fully_transmitted_packet_indixes) > 0) || (num_of_new_multi_packets>0)
                fully_transmitted_packets = obj.get_fully_transmitted_packets(fully_transmitted_packet_indixes);
                    
                %debug
                new_vec = [fully_transmitted_packets new_multi_packet_vec(1:num_of_new_multi_packets)];
                total_packets = 0;
                for i=1:length(new_vec)
                    total_packets = total_packets+new_vec(i).num_of_packets;
                end
                fprintf('Adapter: fully transmitted %d multi packets (%d normal packets)\n',length(fully_transmitted_packet_indixes)+num_of_new_multi_packets, total_packets);
                %debug
                
                obj.owning_node.handle_fully_transmitted_packets([fully_transmitted_packets new_multi_packet_vec(1:num_of_new_multi_packets)]);
            end
            
            %reset the fully transmitted packets vec
            %PR - bad
            obj.clean_packet_transmission_queue(fully_transmitted_packet_indixes);
        end
        
        function fully_transmitted_packets = get_fully_transmitted_packets(obj, fully_transmitted_packets_indixes)
            fully_transmitted_packets = [];
            if length(fully_transmitted_packets_indixes)==0
                return;
            end
            
            num_of_fully_transmitted_packets = length(fully_transmitted_packets_indixes);
            new_vec(1,num_of_fully_transmitted_packets) = MultiPacket();
            fully_transmitted_packets = new_vec;
            k=1;
            for i=1:length(fully_transmitted_packets_indixes)
                multi_packet = obj.packet_transmission_queue(fully_transmitted_packets_indixes(i));
                multi_packet.channel_index = 0;
                assert(multi_packet.packet.is_packet_transmission_complete == true);
                fully_transmitted_packets(k) = multi_packet;
                k=k+1;
            end
        end
        
        function clean_packet_transmission_queue(obj, fully_transmitted_packets_indixes)
            num_of_fully_transmitted_packets = length(fully_transmitted_packets_indixes);
            new_packet_transmission_queue_length = length(obj.packet_transmission_queue)-num_of_fully_transmitted_packets;
            if new_packet_transmission_queue_length == 0
                obj.packet_transmission_queue = [];
                return;
            end
            new_packet_transmission_queue(1,new_packet_transmission_queue_length) = MultiPacket();
            k=1;
            for i=1:length(obj.packet_transmission_queue)
                multi_packet = obj.packet_transmission_queue(i);
                if ~multi_packet.packet.is_packet_transmission_complete
                    new_packet_transmission_queue(k) = multi_packet;
                    k=k+1;
                end
            end
            %remove the fully transmitted packets from the transmission
            %queue
            assert((k-1)==new_packet_transmission_queue_length);
            obj.packet_transmission_queue = new_packet_transmission_queue;
        end
        
        function num = get_number_of_packets_in_transmission_queue(obj)
            num = length(obj.packet_transmission_queue);
        end
        
        function [fully_transmitted_packet_indixes new_multi_packet_vec num_of_new_multi_packets] = transmit_data(obj, time_delta_in_msec)   
            fully_transmitted_packet_indixes = [];
            num_of_fully_transmitted_packets = 0;
            new_multi_packet_vec(1,obj.link_rate_generator.number_of_channels) = MultiPacket();
            num_of_new_multi_packets = 0;
            
            if length(obj.packet_transmission_queue) == 0
                return;
            end
            
            %allocate fully transmitted packets vec
            fully_transmitted_packet_indixes(2*length(obj.packet_transmission_queue)) = 0;
            num_of_fully_transmitted_packets = 0;
            new_multi_packet_vec(1,obj.link_rate_generator.number_of_channels) = MultiPacket();
            num_of_new_multi_packets = 0;
            
            %go over all packets in the transmission queue
            %calculate transmitted data per link
            links_total_remaining_data_in_bytes = [];
            for i=1:obj.link_rate_generator.number_of_channels
                current_link_rate_in_Kbps = obj.link_rate_generator.get_link_rate_in_Kbps(i);
               total_transmitted_data_in_bytes = ((current_link_rate_in_Kbps*(time_delta_in_msec/1000))/8)*1024;
               links_total_remaining_data_in_bytes = [links_total_remaining_data_in_bytes total_transmitted_data_in_bytes];
            end
            
            packet_transmission_queue_size = length(obj.packet_transmission_queue);
            %go over all packets and transmit as possible
             for i=1:packet_transmission_queue_size
                 multi_packet = obj.packet_transmission_queue(i);     
                 packet = multi_packet.packet;
                 curr_link_index = multi_packet.channel_index;
                 curr_link_remaining_bytes = links_total_remaining_data_in_bytes(curr_link_index);
                 if curr_link_remaining_bytes==0
                    continue;
                 end
                 packet_remaining_bytes = packet.size_in_bytes*multi_packet.num_of_packets;
                 if curr_link_remaining_bytes < packet_remaining_bytes
                        obj.number_of_bytes_transferred_per_channel(curr_link_index) = obj.number_of_bytes_transferred_per_channel(curr_link_index) +links_total_remaining_data_in_bytes(curr_link_index);
                        links_total_remaining_data_in_bytes(curr_link_index) = 0;
                        %split multi packet in two
                        num_of_packets_that_can_fit = floor(curr_link_remaining_bytes / packet.size_in_bytes);
                        if num_of_packets_that_can_fit > 0
                            num_of_new_multi_packets = num_of_new_multi_packets+1;
                            new_multi_packet_vec(num_of_new_multi_packets) = multi_packet.split_multi_packet(multi_packet.packet_id_range_min+num_of_packets_that_can_fit);
                            new_multi_packet_vec(num_of_new_multi_packets).packet.is_packet_transmission_complete = true;
                        end
                 else
                        obj.number_of_bytes_transferred_per_channel(curr_link_index) = obj.number_of_bytes_transferred_per_channel(curr_link_index) +packet_remaining_bytes;
                        packet.is_packet_transmission_complete = true;
                        links_total_remaining_data_in_bytes(curr_link_index) = curr_link_remaining_bytes - packet_remaining_bytes;
                        %add packet to fully transmitted packets
                        fully_transmitted_packet_indixes(num_of_fully_transmitted_packets+1) = i;
                        num_of_fully_transmitted_packets = num_of_fully_transmitted_packets+1;
                 end
             end
             fully_transmitted_packet_indixes = fully_transmitted_packet_indixes(1:num_of_fully_transmitted_packets);
        end
        
        function ret=is_adapter_idle(obj)
            ret=(length(obj.packet_transmission_queue) == 0);
        end
    end
    
end

